package uk.org.fuzelogik.eldaw.auth;

import org.apache.wicket.RestartResponseException;
import org.apache.wicket.Session;
import org.apache.wicket.markup.html.WebPage;

import uk.org.fuzelogik.eldaw.wicket.IAuthenticatedSession;
import uk.org.fuzelogik.eldaw.wicket.component.IncompatibleSessionException;


/**
 * Base class for pages that are returned-to by third-party authentication
 * services following the successful or unsuccessful authentication of a user.
 * 
 * @author cgdavies
 */
public abstract class DelegatedAuthenticationReturnPage extends WebPage
{
	public DelegatedAuthenticationReturnPage()
	{
		IAuthenticatedSession authSession = getAuthenticatedSession();
		IAuthenticationStrategy strategy = authSession
			.getAuthenticationStrategy();

		// No strategy means we're not authenticating. Similarly, if it's not
		// a delegated authentication strategy, we're not authenticating.
		if( ( strategy == null )
			|| ( !( strategy instanceof IDelegatedAuthenticationStrategy ) ) )
			throw new RestartResponseException( getNotAuthenticatingPage() );

		IDelegatedAuthenticationStrategy dastrategy = (IDelegatedAuthenticationStrategy)strategy;

		// Check that authentication is in progress and the strategy's
		// compatible.
		if( !dastrategy.isAuthenticationInProcess()
			|| !isAuthenticationStrategyCompatible( dastrategy ) )
			throw new RestartResponseException( getNotAuthenticatingPage() );

		try
		{
			// Update the strategy.
			final String identity = getAuthenticatedIdentifier( dastrategy );
			dastrategy.setAuthenticatedIdentity( identity );

			// Redirect appropriately.
			if( identity == null )
				throw new RestartResponseException(
					getAuthenticationFailurePage() );
			else
				throw new RestartResponseException(
					getAuthenticationSuccessPage() );
		}
		catch( MalformedRequestException ex )
		{
			throw new RestartResponseException( getNotAuthenticatingPage() );
		}
	}

	/**
	 * Checks if the authentication request was successful.
	 * 
	 * @param strategy the authentication strategy in the session. Before this
	 * method is invoked, compatibility will have been tested with
	 * {@link #isAuthenticationStrategyCompatible(IDelegatedAuthenticationStrategy)}.
	 * There is no need to update this object yourself; just use it for reading
	 * @return the identifier that the user successfully authenticated as, or
	 * <code>null</code> if authentication was unsuccessful
	 * @throws MalformedRequestException if the page parameters contain invalid
	 * or missing information
	 */
	protected abstract String getAuthenticatedIdentifier(
		IDelegatedAuthenticationStrategy strategy )
		throws MalformedRequestException;

	/**
	 * Checks if the authentication strategy held in the user's session is
	 * compatible with this return-page.
	 * 
	 * @param strategy the strategy instance held in the user's session
	 * @return <code>true</code> if this return page knows how to interpret
	 * the request in relation to this strategy
	 */
	protected abstract boolean isAuthenticationStrategyCompatible(
		IDelegatedAuthenticationStrategy strategy );

	/**
	 * @return the class of the page to redirect to if authentication was
	 * successful
	 */
	protected abstract Class getAuthenticationSuccessPage();

	/**
	 * @return the class o the page to redirect to if authentication was
	 * unsuccessful
	 */
	protected abstract Class getAuthenticationFailurePage();

	/**
	 * @return the class of the page to redirect to if no authentication is
	 * currently in progress. The default behaviour is to redirect to the
	 * application's home page
	 */
	protected Class getNotAuthenticatingPage()
	{
		return getApplication().getHomePage();
	}

	/**
	 * Gets the current user's authenticated session instance.
	 * 
	 * @return the current user's authenticated session instance
	 */
	private IAuthenticatedSession getAuthenticatedSession()
	{
		Session session = getSession();

		if( !( session instanceof IAuthenticatedSession ) )
			throw new IncompatibleSessionException();

		return (IAuthenticatedSession)session;
	}

	/**
	 * Thrown if authentication can't be checked because the page parameters
	 * don't contain the correct data.
	 * 
	 * @author cgdavies
	 */
	public static class MalformedRequestException extends Exception
	{
		public MalformedRequestException()
		{
			super();
		}

		public MalformedRequestException( String message, Throwable cause )
		{
			super( message, cause );
		}

		public MalformedRequestException( String message )
		{
			super( message );
		}

		public MalformedRequestException( Throwable cause )
		{
			super( cause );
		}
	}
}
